package config.mybatis;

import org.apache.commons.lang3.StringUtils;

import bhbt.utils.StringUtil;






import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.*;
 
/**
 *  自动生成MyBatis的实体映射XML文件
 * @author   Qiyh
 * @date     2015-09-16
 * @version  v1.0
 */
public class MybatisBuilder {
 
    /**
     **********************************使用前必读*******************
     **
     ** 使用前请将bean_package更改为自己，其他无须改动。
     ** 如果beanName为空，则生成bean_package下所有bean的xml
     **
     ***********************************************************
     */
	private final static String SQL_DIALECT = "MYSQL";
	private String bean_package = "bhbt.model";
	private String beanName = "Daili.java";
	
    private final static String outputPath = "D:/Users/lenovo/Desktop/MybatisBuilder/";
    private final static String output_xml = outputPath + "xml";
    private final static String xml_ext = ".map.xml";//文件后缀名
    private final static boolean output_overrid = true;//是否覆盖
    private final static boolean col_Camel = false;;//数据库表字段是否为驼峰结构（A_B_C格式）
 
    private boolean childPackage = false;//是否查询子目录(子包)
    private Map<Class, Map<String,String>> classFieldMap = new HashMap<Class, Map<String,String>>();
    
    /*
     * beanType与jdbcType对应关系
     */
    private Map<String, String> fieldTypeMap = new HashMap<String, String>(){{
    	put("char", "CHAR");
    	put("String", "VARCHAR");
    	put("Date", "TIMESTAMP");//Date
    	put("int", "INTEGER");
    	put("Integer", "INTEGER");
    	put("float", "NUMERIC");
    	put("Float", "NUMERIC");
    	put("double", "NUMERIC");
    	put("Double", "NUMERIC");
    	put("long", "NUMERIC");
    	put("Long", "NUMERIC");
    	put("BigDecimal", "DECIMAL");
    }};
    
    private void init() {
    	Set<String> clazzList = getClassList();
    	for (String clazzName : clazzList) {
    		Class clazz = getClassByFullName(clazzName);
    		if(null == clazz){
    			System.out.println("获取Class为空："+clazzName);
    			return;
    		}
    		LinkedHashMap<String, String> fieldMap = new LinkedHashMap<String, String>();
    		Class superClass = clazz.getSuperclass();
    		if(null != superClass){
    			Field[] superFields = superClass.getDeclaredFields();
    			for (Field field : superFields) {
        			String fieldName = field.getName();
					if("serialVersionUID".equalsIgnoreCase(fieldName)){
						continue;
					}
        			String fieldType = field.getType().getSimpleName();
        			String jdbcType = fieldTypeMap.get(fieldType);
        			if(null != jdbcType){
        				fieldMap.put(fieldName, jdbcType);
        			}
    			}
    		}
    		Field[] fields = clazz.getDeclaredFields();
    		for (Field field : fields) {
    			String fieldName = field.getName();
				if("serialVersionUID".equalsIgnoreCase(fieldName)){
					continue;
				}
    			String fieldType = field.getType().getSimpleName();
    			String jdbcType = fieldTypeMap.get(fieldType);
    			if(null != jdbcType){
    				fieldMap.put(fieldName, jdbcType);
    			}
			}
    		classFieldMap.put(clazz, fieldMap);
		}
    }
    
    /**
     * @Title: getClassList
     * @return Set
     * @author: Qiyh 2015-09-16
     */
    Set<String> classList;
     public Set<String> getClassList() {
    	 Set<String> classList = new HashSet<String>();
         if(bean_package.toLowerCase().endsWith(".java")){
        	 bean_package = bean_package.substring(0,bean_package.length()-5);
        	 beanName = bean_package.substring(bean_package.lastIndexOf(".")+1)+".java";
    		 bean_package = bean_package.substring(0, bean_package.lastIndexOf("."));
    	 }
         if(beanName == null){
        	 ClassLoader loader = Thread.currentThread().getContextClassLoader();
             String packagePath = bean_package.replace(".", "/");
             URL url = loader.getResource(packagePath);
             if (url != null) {
                 if ("file".equals(url.getProtocol())) {
                     classList.addAll(getClassNameByFile(url.getPath(), null));
                 }
             }
         }
         else{
        	 String packagePath = bean_package.replace(".", "/");
        	 classList.add(packagePath + "/" + beanName.replace(".java", ""));
         }
         return classList;
     }

	/**
	 * 从项目文件获取某包下所有类
	 * @param filePath 文件路径
	 * @param className 类名集合
	 * @return 类的完整名称
	 * @author qiyh
	 */
	private Set<String> getClassNameByFile(String filePath, Set<String> className) {
	    Set<String> myClassName = new HashSet<String>();
	    filePath = filePath.replaceAll("%20", " ");
	    File file = new File(filePath);
	    File[] childFiles = file.listFiles();
	    for (File childFile : childFiles) {
	        if (childFile.isDirectory()) {
	            if (childPackage) {
	                myClassName.addAll(getClassNameByFile(childFile.getPath(), myClassName));
	            }
	        } else {
	            String childFilePath = childFile.getPath();
	            if (childFilePath.endsWith(".class")) {
	                childFilePath.replaceAll("\\\\", "/");
	                childFilePath = childFilePath.substring(childFilePath.indexOf("/classes") + 9, childFilePath.lastIndexOf("."));
	                childFilePath = childFilePath.replace("/", ".");
	                myClassName.add(childFilePath);
	            }
	        }
	    }
	    return myClassName;
	}
	
	/**
     * 根据class名字找到对应的Class
     * @param classFullName 全名
     * @return Class
     * @author qiyh
     */
    public Class getClassByFullName(String classFullName) {
        Class clazz = null;
        try {
            if (StringUtils.isNotBlank(classFullName)) {
                classFullName = classFullName.replaceAll("\\\\", "/");
                if (0 < classFullName.indexOf("/classes")) {
                    classFullName = classFullName.substring(classFullName.indexOf("/classes") + 9);
                }
                clazz = Class.forName(classFullName.replaceAll("/", "."));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return clazz;
    }
    
    
    /**
     *  构建实体类映射XML文件
     * @param clazz
     * @param fieldMap
     * @throws IOException
     */
    private boolean buildMapperXml(Class clazz, Map<String,String> fieldMap) throws IOException {
    	// 
        File folder = new File(output_xml);
        if (!folder.exists() ) {
            folder.mkdirs();
        }
        String clazShortName = clazz.getSimpleName();
        File mapperXmlFile = new File(output_xml, clazShortName + xml_ext);
        System.out.print("正在生成："+mapperXmlFile.getName()+"...");
        if(!output_overrid && mapperXmlFile.exists()){
        	System.out.println("已存在,跳过!");
        	return false;
        }
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(mapperXmlFile)));
        bw.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
        bw.newLine();
        bw.write("<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">");
        bw.newLine();
        bw.write("<mapper namespace=\"" + clazShortName + "\">");

		//开启缓存
		bw.newLine();
		bw.write("\t<cache eviction=\"FIFO\" flushInterval=\"60000\" size=\"512\" readOnly=\"true\" />");

		// 下面开始写SqlMapper中的方法
		buildSQL_select(bw, clazShortName, fieldMap);
        buildSQL_insert(bw, clazShortName, fieldMap);
        buildSQL_update(bw, clazShortName, fieldMap);
        buildSQL_sumSql(bw, clazShortName, fieldMap);
        buildSQL_delete(bw, clazShortName, fieldMap);
 
        bw.newLine();
        bw.write("</mapper>");
        bw.flush();
        bw.close();
        System.out.println("完成!");
        return true;
    }
 
    private void buildSQL_select(BufferedWriter bw, String clazzName, Map<String,String> fieldMap) throws IOException {
    	String tableName = StringUtil.toUnderlineName(clazzName, "_").toUpperCase();
    	Set<Map.Entry<String, String>> fieldSet = fieldMap.entrySet();
    	if(null==fieldSet || 0==fieldSet.size()){
    		return;
    	}
    	 // 通用查询sql
        bw.newLine();
        bw.write("\t<sql id=\"selectSql\">");
        bw.newLine();
        bw.write("\t\t SELECT");
        
        // 字段列
		Object[] colArr = fieldMap.keySet().toArray();
		for(int i=0; i<colArr.length; i++){
			bw.newLine();
			String field = colArr[i].toString();
			String col = field;
			if(col_Camel){
				col = StringUtil.toCamelCase(col).toUpperCase();
			}
			bw.write("\t\t\t "+col+" as "+field);
			if(i != colArr.length-1){
				bw.write(",");
			}
		}
        bw.newLine();
        bw.write("\t\t FROM "+tableName);
        bw.newLine();
        bw.write("\t</sql>");
        bw.newLine();
        
        // 通用查询条件
        bw.newLine();
        bw.write("\t<sql id=\"selectSqlCdt\">");
        bw.newLine();
        bw.write("\t\t<where>");
    	for (Map.Entry<String, String> fieldEntry : fieldSet) {
    		bw.newLine();
    		String field = fieldEntry.getKey();
    		String col = field;
    		if(col_Camel){
    			col = StringUtil.toCamelCase(col).toUpperCase();
    		}
    		String jdbcType = fieldEntry.getValue().toUpperCase();
    		if("NUMERIC".equals(jdbcType) || "INTEGER".equals(jdbcType) 
    				|| "LONG".equals(jdbcType) || "FLOAT".equals(jdbcType) 
    				|| "DOUBLE".equals(jdbcType) || "DECIMAL".equals(jdbcType)){
    			 bw.write("\t\t\t<if test=\"null!="+col+" and "+col+">0\">AND "+col+" = #{"+field+",jdbcType=NUMERIC}</if>");
    		}else{
    			bw.write("\t\t\t<if test=\"null!="+col+" and "+col+"!=''\">AND "+col+" = #{"+field+",jdbcType="+jdbcType+"}</if>");
    		}
		}
		bw.newLine();
		bw.write("\t\t\t<if test=\"null!=where and where!=''\">AND ${where}</if>");
		bw.newLine();
		bw.write("\t\t</where>");
		bw.newLine();
		bw.write("\t</sql>");
		bw.newLine();
		
		// 根据主键返回对象
		bw.newLine();
		bw.write("\t<select id=\"getByKey\" parameterType=\"int\" resultType=\""+clazzName+"\">");
		bw.newLine();
		bw.write("\t\t<include refid=\"selectSql\" />");
		if(fieldMap.containsKey("id")){
			bw.newLine();
			bw.write("\t\t WHERE ID = #{id,jdbcType="+fieldMap.get("id")+"}");
		}
		bw.newLine();
		bw.write("\t</select>");
		bw.newLine();
		
		// 动态查询
		bw.newLine();
		bw.write("\t<select id=\"select\" resultType=\""+clazzName+"\">");
		bw.newLine();
		bw.write("\t\t<include refid=\"selectSql\" />");
		bw.newLine();
		bw.write("\t\t<include refid=\"selectSqlCdt\" />");
		bw.newLine();
		bw.write("\t</select>");
		bw.newLine();
		
		// 分页查询
		bw.newLine();
		bw.write("\t<select id=\"paginate\" resultType=\"java.util.HashMap\">");// resultType=""+clazzName+""
		bw.newLine();
		bw.write("\t\t<include refid=\"Page.prefix\" />");
		bw.newLine();
		bw.write("\t\t\t<include refid=\"selectSql\" />");
		bw.newLine();
		bw.write("\t\t\t<include refid=\"selectSqlCdt\" />");
		bw.newLine();
		bw.write("\t\t<include refid=\"Page.suffix\" />");
		bw.newLine();
		bw.write("\t</select>");
		bw.newLine();
		
		// 
		bw.newLine();
		bw.write("\t<select id=\"paginate_count\" resultType=\"int\">");
		bw.newLine();
		bw.write("\t\tSELECT COUNT(0) FROM (");
		bw.newLine();
		bw.write("\t\t\t<include refid=\"selectSql\" />");
		bw.newLine();
		bw.write("\t\t\t<include refid=\"selectSqlCdt\" />");
		bw.newLine();
		bw.write("\t\t)T");
		bw.newLine();
		bw.write("\t</select>");
		bw.newLine();
    }
    
    private void buildSQL_insert(BufferedWriter bw, String clazzName, Map<String,String> fieldMap) throws IOException {
    	String tableName = StringUtil.toUnderlineName(clazzName, "_").toUpperCase();
    	Set<Map.Entry<String, String>> fieldSet = fieldMap.entrySet();
    	if(null==fieldSet || 0==fieldSet.size()){
    		return;
    	}
    	bw.newLine();
    	bw.write("\t<insert id=\"insert\" parameterType=\""+clazzName+"\">");
    	bw.newLine();
		if(SQL_DIALECT.equalsIgnoreCase("oracle")) {
			bw.write("\t\t<selectKey keyProperty=\"id\" resultType=\"int\" order=\"BEFORE\">");
			bw.newLine();
			bw.write("\t\t\t SELECT SEQ_" + tableName + ".NEXTVAL FROM DUAL");
			bw.newLine();
			bw.write("\t\t</selectKey>");
		}
		else if(SQL_DIALECT.equalsIgnoreCase("mysql")){
			bw.write("\t\t<selectKey keyProperty=\"id\" resultType=\"int\" order=\"AFTER\">");
			bw.newLine();
			bw.write("\t\t\t select LAST_INSERT_ID()");
			bw.newLine();
			bw.write("\t\t</selectKey>");
		}
    	bw.newLine();
		bw.write("\t\t INSERT INTO "+tableName+"(");
		// 字段列
		Object[] colArr = fieldMap.keySet().toArray();
		for(int i=0; i<colArr.length; i++){
			bw.newLine();
			String col = colArr[i].toString();
			if(col_Camel){
				col = StringUtil.toCamelCase(col).toUpperCase();
			}
			bw.write("\t\t\t "+col);
			if(i != colArr.length-1){
				bw.write(",");
			}
		}
		bw.newLine();
		bw.write("\t\t )");
		bw.newLine();
		bw.write("\t\t VALUES(");
		// 字段值
		for(int i=0; i<colArr.length; i++){
			String col = colArr[i].toString();
			if(col_Camel){
				col = StringUtil.toCamelCase(col).toUpperCase();
			}
			String jdbcType = fieldMap.get(col);
			bw.newLine();
			if(colArr[i].toString().equalsIgnoreCase("createDate")){
				if(SQL_DIALECT.equalsIgnoreCase("oracle")){
					bw.write("\t\t\t systimestamp");
				}
				else if(SQL_DIALECT.equalsIgnoreCase("mysql")){
					bw.write("\t\t\t now()");
				}
				else{
					bw.write("\t\t\t #{"+col+",jdbcType="+jdbcType+"}");
				}
			}else{
				bw.write("\t\t\t #{"+col+",jdbcType="+jdbcType+"}");
			}
			if(i != colArr.length-1){
				bw.write(",");
			}
		}
		bw.newLine();
		bw.write("\t\t )");
		bw.newLine();
		bw.write("\t</insert>");
		bw.newLine();
		
		// insert_batch
		bw.newLine();
		bw.write("\t<!-- batch_insert: must use select tag -->");
		bw.newLine();
		bw.write("\t<select id=\"insertBatch\" parameterType=\"java.util.List\">");
		bw.newLine();
		bw.write("\t\t INSERT INTO "+tableName+"(");
		// 字段列
		for(int i=0; i<colArr.length; i++){
			bw.newLine();
			String col = colArr[i].toString();
			if(col_Camel){
				col = StringUtil.toCamelCase(col).toUpperCase();
			}
			bw.write("\t\t\t "+col);
			if(i != colArr.length-1){
				bw.write(",");
			}
		}
		bw.newLine();
		if(SQL_DIALECT.equalsIgnoreCase("ORACLE")){
			bw.write("\t\t ) SELECT SEQ_"+tableName+".NEXTVAL,");
		}
		bw.write("\t\t ) T.* FROM (");
		bw.newLine();
		String clazzColl = StringUtil.toLowerCaseFirstOne(clazzName)+"s";
		bw.write("\t\t<foreach collection=\""+clazzColl+"\" item=\"item\" index=\"index\" separator=\"UNION ALL\">");
		bw.newLine();
		bw.write("\t\t\t SELECT");
		// 字段值
		for(int i=0; i<colArr.length; i++){
			String col = colArr[i].toString();
			if(col.equalsIgnoreCase("id")) continue;
			if(col_Camel){
				col = StringUtil.toCamelCase(col).toUpperCase();
			}
			String jdbcType = fieldMap.get(col);
			bw.newLine();
			if(colArr[i].toString().equalsIgnoreCase("createDate")){
				if(SQL_DIALECT.equalsIgnoreCase("oracle")){
					bw.write("\t\t\t\tsystimestamp as "+col);
				}
				else if(SQL_DIALECT.equalsIgnoreCase("mysql")){
					bw.write("\t\t\t\tnow() as "+col);
				}
				else{
					bw.write("\t\t\t\t#{item."+col+",jdbcType="+jdbcType+"} as "+col);
				}
			}else{
				bw.write("\t\t\t\t#{item."+col+",jdbcType="+jdbcType+"} as "+col);
			}
			if(i != colArr.length-1){
				bw.write(",");
			}
		}

		if(SQL_DIALECT.equalsIgnoreCase("ORACLE")) {
			bw.newLine();
			bw.write("\t\t\t FROM DUAL");
		}
		bw.newLine();
		bw.write("\t\t</foreach>");
		bw.newLine();
		bw.write("\t\t)T");
		bw.newLine();
		bw.write("\t</select>");
		bw.newLine();
    }
    
    private void buildSQL_update(BufferedWriter bw,String clazzName, Map<String,String> fieldMap) throws IOException {
    	String tableName = StringUtil.toUnderlineName(clazzName, "_").toUpperCase();
    	Set<Map.Entry<String, String>> fieldSet = fieldMap.entrySet();
    	if(null==fieldSet || 0==fieldSet.size()){
    		return;
    	}
    	bw.newLine();
    	bw.write("\t<update id=\"update\">");
    	bw.newLine();
    	bw.write("\t\t UPDATE "+tableName);
    	bw.newLine();
    	bw.write("\t\t<set>");
    	for (Map.Entry<String, String> fieldEntry : fieldSet) {
    		bw.newLine();
    		String field = fieldEntry.getKey();
    		String col = field;
    		if(col_Camel){
    			col = StringUtil.toCamelCase(col).toUpperCase();
    		}
    		String jdbcType = fieldEntry.getValue();
			bw.write("\t\t\t<if test=\"null!="+col+"\">"+col+" = #{"+field+",jdbcType="+jdbcType+"},</if>");
		}
    	bw.newLine();
    	bw.write("\t\t\t<if test=\"null!=diyUpdate and diyUpdate!=''\">${diyUpdate}</if>");
    	bw.newLine();
    	bw.write("\t\t</set>");
    	bw.newLine();
    	bw.write("\t\t<where>");
    	bw.newLine();
    	bw.write("\t\t\t<if test=\"null!=id and id>0\">AND id = #{id,jdbcType=NUMERIC}</if>");
    	bw.newLine();
    	bw.write("\t\t\t<if test=\"null!=where and where!=''\">AND ${where}</if>");
    	bw.newLine();
    	bw.write("\t\t</where>");
    	bw.newLine();
    	bw.write("\t</update>");
    	bw.newLine();
    }
    
    
    private void buildSQL_sumSql(BufferedWriter bw, String clazzName, Map<String,String> fieldMap) throws IOException {
    	String tableName = StringUtil.toUnderlineName(clazzName, "_").toUpperCase();
    	Set<Map.Entry<String, String>> fieldSet = fieldMap.entrySet();
    	if(null==fieldSet || 0==fieldSet.size()){
    		return;
    	}
    	bw.newLine();
    	bw.write("\t<select id=\"sumSql\" resultType=\"Double\">");
    	bw.newLine();
    	bw.write("\t\t SELECT ");
    	bw.newLine();
    	
    	
    	bw.write("\t\t\t<if test=\"null!=diyUpdate and diyUpdate!=''\">${diyUpdate}</if>");
    	
    	bw.newLine();
    	bw.write("\t\t FROM "+tableName);
    	bw.newLine();
    	
    	bw.write("\t\t<where>");
    	bw.newLine();
    	bw.write("\t\t\t<if test=\"null!=id and id>0\">AND id = #{id,jdbcType=NUMERIC}</if>");
    	bw.newLine();
    	bw.write("\t\t\t<if test=\"null!=where and where!=''\">AND ${where}</if>");
    	bw.newLine();
    	bw.write("\t\t</where>");
    	bw.newLine();
    	bw.write("\t</select>");
    	bw.newLine();
    }
    
    private void buildSQL_delete(BufferedWriter bw, String clazzName, Map<String,String> fieldMap) throws IOException {
    	String tableName = StringUtil.toUnderlineName(clazzName, "_").toUpperCase();
    	Set<Map.Entry<String, String>> fieldSet = fieldMap.entrySet();
    	if(null==fieldSet || 0==fieldSet.size()){
    		return;
    	}
    	// delete
    	bw.newLine();
    	bw.write("\t<delete id=\"delete\">");
    	bw.newLine();
    	bw.write("\t\t DELETE FROM "+tableName+" WHERE ID=#{id,jdbcType=NUMERIC}");
    	bw.newLine();
    	bw.write("\t</delete>");
    	bw.newLine();
    	
    	// delete batch
    	bw.newLine();
    	bw.write("\t<delete id=\"deleteBatch\">");
    	bw.newLine();
    	bw.write("\t\t DELETE FROM "+tableName+" WHERE ID in (${ids})");
    	bw.newLine();
    	bw.write("\t</delete>");
    	bw.newLine();
    }
 
 
    public void generate() {
        init();
        int success = 0, error = 0, pass = 0;
        Set<Map.Entry<Class,Map<String,String>>> fieldSet = classFieldMap.entrySet();
        if(null!=fieldSet && 0<fieldSet.size()){
        	// String: className; Map:字段名/字段类型
        	for (Map.Entry<Class,Map<String,String>> entry : fieldSet) {
        		try {
        			boolean flag = buildMapperXml(entry.getKey(), entry.getValue());
        			if(flag)success++;else pass++;
				} catch (Exception e) {
					e.printStackTrace();
					error++;
				}
			}
        }
        System.out.print("\r\n>>已生成 "+fieldSet.size()+"个mybatis-mapper.xml文件!");
        System.out.println(" 成功："+success+"个; 失败："+error+"个; 跳过："+pass+"个");
    }
 
    public static void main( String[] args ) {
        try {
        	new MybatisBuilder().generate();
        	// 自动打开生成文件的目录
        	String openFolder = outputPath.replaceAll("/", "\\\\"); 
			Runtime.getRuntime().exec("cmd /c start explorer "+openFolder);
		} catch (Exception e) {
			e.printStackTrace();
		}
    }
}